﻿//   Parser.cs
//
//   Copyright 2009 Eric Llewellyn
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LibEELbeta
{
    public delegate void SyntaxErrorDelegate(string msg, Token t);

    public class Parser
    {
        private List<Token> tokens;
        private bool waitingForInput;
        public event SyntaxErrorDelegate SyntaxError;

        public bool WaitingForInput
        {
            get { return waitingForInput; }
        }

        public Parser()
        {
            tokens = new List<Token>();
        }

        //Add "tokens" to the buffer (Except "Unknown" and "Comment" tokens)
        //Get statements untill NULL is reached
        //Return all the statements read
        public SyntaxNode[] ParseTokens(Token[] tokens)
        {
            List<SyntaxNode> syntaxNodes = new List<SyntaxNode>();
            SyntaxNode n;

            foreach (Token t in tokens)
                if (t.tokenType != TokenTypes.Unknown && t.tokenType != TokenTypes.Comment)
                    this.tokens.Add(t);

            do
            {
                n = GetStatement();
                if (n != null)
                    syntaxNodes.Add(n);
            } while (n != null);

            return syntaxNodes.ToArray();
        }

        //------------

        //Try to parse a statements
        //If we're out of, set "pos" back to the start and return null
        //If there's an error, skip to the end and return null
        //Otherwise return the statement
        public SyntaxNode GetStatement()
        {
            int oldPos = pos;
            SyntaxNode n = null;

            try
            {
                n = parseStat();

                tokens = tokens.GetRange(pos, tokens.Count - pos);
                pos = 0;
                waitingForInput = false;
            }
            catch (ParserOutOfTokensException e)
            {
                if (pos != oldPos)
                {
                    pos = oldPos;
                    waitingForInput = true;
                }
            }
            catch (Exception e)
            {
                SyntaxError.Invoke(e.Message, TokenAt(0));
                
                pos = 0;
                tokens.Clear();
                waitingForInput = false;
            }
            return n;
        }

        //------------

        private int pos;

        //Returns the token at "pos + offset" in the buffer
        //Calls Error if not enough tokens are available
        private Token TokenAt(int offset)
        {
            if ((pos + offset) < tokens.Count)
                return tokens[(pos + offset)];
            else
            {
                throw new ParserOutOfTokensException();
            }
        }

        private void Error(string msg)
        {
            throw new Exception(msg);
        }

        private Token Expect(TokenTypes expectedType, string typeName)
        {
            if (TokenAt(0).tokenType == expectedType)
            {
                pos++;
                return TokenAt(-1);
            }
            else
            {
                Error(typeName + " expected");
                return null;
            }
        }

        //------------

        //<stats>       ::= <stat> <statTail>
        private SyntaxNode parseStats()
        {
            return parseStatTail(parseStat());
        }

        //<statTail>    ::= ""
        //                | <stats>
        private SyntaxNode parseStatTail(SyntaxNode a)
        {
            int oldPos = pos;
            SyntaxNode n1;
            SyntaxNode stats;

            try
            {
                n1 = parseStats();
                stats = new SyntaxNode(SyntaxNodeTypes.Statements, null);
                stats.operands.Add(a);
                stats.operands.Add(n1);
            }
            catch (Exception ex)
            {
                pos = oldPos;
                return a;
            }

            return stats;
        }

        //<stat>        ::= "return" <exp> ";"
        //                | "write" "(" <exp> ")" ";"
        //                | "read" "(" <exp> ")" ";"
        //                | "if" "(" <exp> ")" <stats> "else" <stats> "end"
        //                | "loop" <stats> "while" "(" <exp> ")"
        //                | <exp> ";"
        private SyntaxNode parseStat()
        {
            TokenTypes t1 = TokenAt(0).tokenType;
            SyntaxNode stat = null;

            if (t1 == TokenTypes.Return)
            {
                pos++;
                stat = parseExp();
                Expect(TokenTypes.SemiColon, ";");
            }
            else if (t1 == TokenTypes.Write)
            {
                pos++;
                Expect(TokenTypes.LBracket, "(");
                SyntaxNode n3 = parseExp();
                Expect(TokenTypes.RBracket, ")");
                Expect(TokenTypes.SemiColon, ";");

                stat = new SyntaxNode(SyntaxNodeTypes.Write, null);
                stat.operands.Add(n3);
            }
            else if (t1 == TokenTypes.Read)
            {
                pos++;
                Expect(TokenTypes.LBracket, "(");
                SyntaxNode n3 = parseExp();
                Expect(TokenTypes.RBracket, ")");
                Expect(TokenTypes.SemiColon, ";");

                stat = new SyntaxNode(SyntaxNodeTypes.Read, null);
                stat.operands.Add(n3);
            }
            else if (t1 == TokenTypes.If)
            {
                pos++;
                Expect(TokenTypes.LBracket, "(");
                SyntaxNode n3 = parseExp();
                Expect(TokenTypes.RBracket, ")");
                SyntaxNode n5 = parseStats();
                Expect(TokenTypes.Else, "else");
                SyntaxNode n7 = parseStats();
                Expect(TokenTypes.End, "end");

                stat = new SyntaxNode(SyntaxNodeTypes.If, null);
                stat.operands.Add(n3);
                stat.operands.Add(n5);
                stat.operands.Add(n7);
            }
            else if (t1 == TokenTypes.Loop)
            {
                pos++;
                SyntaxNode n2 = parseStats();
                Expect(TokenTypes.While, "while");
                Expect(TokenTypes.LBracket, "(");
                SyntaxNode n5 = parseExp();
                Expect(TokenTypes.RBracket, ")");

                stat = new SyntaxNode(SyntaxNodeTypes.Loop, null);
                stat.operands.Add(n2);
                stat.operands.Add(n5);
            }
            else
            {
                SyntaxNode n1 = parseExp();
                Expect(TokenTypes.SemiColon, ";");

                stat = new SyntaxNode(SyntaxNodeTypes.Discard, null);
                stat.operands.Add(n1);
            }

            return stat;
        }

        //<termTail>    ::= "*" <term>
        //                | "/" <term>
        //                | ""
        private SyntaxNode parseTermTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Times)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Mul, null);
                n.operands.Add(a);
                n.operands.Add(parseTerm());
                return n;
            }
            else if (t1 == TokenTypes.Div)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Div, null);
                n.operands.Add(a);
                n.operands.Add(parseTerm());
                return n;
            }
            else
            {
                return a;
            }
        }

        //<term>        ::= <val> <termTail>
        private SyntaxNode parseTerm()
        {
            return parseTermTail(parseVal());
        }

        //<arithTail>   ::= "+" <arith>
        //                | "-" <arith>
        //                | ""
        private SyntaxNode parseArithTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Plus)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Add, null);
                n.operands.Add(a);
                n.operands.Add(parseArith());
                return n;
            }
            else if (t1 == TokenTypes.Minus)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Sub, null);
                n.operands.Add(a);
                n.operands.Add(parseArith());
                return n;
            }
            else
            {
                return a;
            }
        }

        //<arith>       ::= <term> <arithTail>
        private SyntaxNode parseArith()
        {
            return parseArithTail(parseTerm());
        }

        //<expTail>     ::= "==" <exp>
        //                | "!=" <exp>
        //                | "<"  <exp>
        //                | "<=" <exp>
        //                | ">"  <exp>
        //                | ">=" <exp>
        //                | ""
        private SyntaxNode parseExpTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Eq)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Eq, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.NEq)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.NEq, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.Lt)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Lt, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.Le)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Le, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.Gt)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Gt, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.Ge)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Ge, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else
            {
                return a;
            }
        }

        //<exp>         ::= <arith> <expTail>
        private SyntaxNode parseExp()
        {
            return parseExpTail(parseArith());
        }

        //<paramTail>   ::= "," <params>
        //                | ""
        private SyntaxNode parseParamTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Comma)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Statements, null);
                n.operands.Add(a);
                n.operands.Add(parseParams());
                return n;
            }
            else
            {
                return a;
            }
        }

        //<params>      ::= <exp> <paramTail>
        //                | ""
        private SyntaxNode parseParams()
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.RBracket)
                return null;

            return parseParamTail(parseExp());
        }

        //<argTail>     ::= "," <args>
        //                | ""
        private SyntaxNode parseArgTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Comma)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Statements, null);
                n.operands.Add(a);
                n.operands.Add(parseArgs());
                return n;
            }
            else
            {
                return a;
            }
        }

        //<args>        ::= "arg" ID <argTail>
        //              ::= ""
        private SyntaxNode parseArgs()
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.RBracket)
                return null;

            Expect(TokenTypes.Arg, "arg");
            Token t2 = Expect(TokenTypes.Id, "identifier");
            SyntaxNode arg = new SyntaxNode(SyntaxNodeTypes.Arg, "\"" + t2.value + "\"");

            return parseArgTail(arg);
        }

        //<varTail>     ::= "=" <exp>
        //                | "(" <params> ")"
        //                | ""
        private SyntaxNode parseVarTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Assign)
            {
                pos++;
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Assign, null);
                n.operands.Add(a);
                n.operands.Add(parseExp());
                return n;
            }
            else if (t1 == TokenTypes.LBracket)
            {
                pos++;
                SyntaxNode n2 = parseParams();
                Expect(TokenTypes.RBracket, ")");
                SyntaxNode n = new SyntaxNode(SyntaxNodeTypes.Call, null);
                n.operands.Add(a);
                if (n2 != null)
                    n.operands.Add(n2);
                return n;
            }
            else
            {
                return a;
            }
        }

        //<val>         ::= NUMBER | STRING | BOOL | NULL | TABLE
        //                | "-" <val>
        //                | "!" <val>
        //                | "(" <exp> ")"
        //                | "function" "(" <args> ")" <stats> "end"
        //                | ID <varTail>
        private SyntaxNode parseVal()
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.Number)
            {
                pos++;
                return new SyntaxNode(SyntaxNodeTypes.Constant, TokenAt(-1).value);
            }
            else if (t1 == TokenTypes.String)
            {
                pos++;
                return new SyntaxNode(SyntaxNodeTypes.Constant, TokenAt(-1).value);
            }
            else if (t1 == TokenTypes.Bool)
            {
                pos++;
                return new SyntaxNode(SyntaxNodeTypes.Constant, TokenAt(-1).value);
            }
            else if (t1 == TokenTypes.Null)
            {
                pos++;
                return new SyntaxNode(SyntaxNodeTypes.Constant, TokenAt(-1).value);
            }
            else if (t1 == TokenTypes.Table)
            {
                pos++;
                return new SyntaxNode(SyntaxNodeTypes.Constant, TokenAt(-1).value);
            }
            else if (t1 == TokenTypes.Minus)
            {
                pos++;
                SyntaxNode neg = new SyntaxNode(SyntaxNodeTypes.Neg, null);
                neg.operands.Add(parseVal());
                return neg;
            }
            else if (t1 == TokenTypes.Not)
            {
                pos++;
                SyntaxNode not = new SyntaxNode(SyntaxNodeTypes.Not, null);
                not.operands.Add(parseVal());
                return not;
            }
            else if (t1 == TokenTypes.LBracket)
            {
                pos++;
                SyntaxNode n = parseExp();
                Expect(TokenTypes.RBracket, ")");
                return n;
            }
            else if (t1 == TokenTypes.Function)
            {
                pos++;
                Expect(TokenTypes.LBracket, "(");
                SyntaxNode n3 = parseArgs();
                Expect(TokenTypes.RBracket, ")");
                SyntaxNode n5 = parseStats();
                Expect(TokenTypes.End, "end");

                SyntaxNode fn = new SyntaxNode(SyntaxNodeTypes.Function, null);
                if (n3 != null)
                {
                    SyntaxNode stats = new SyntaxNode(SyntaxNodeTypes.Statements, null);
                    stats.operands.Add(n3);
                    stats.operands.Add(n5);
                    fn.operands.Add(stats);
                }
                else
                {
                    fn.operands.Add(n5);
                }

                return fn;
            }
            else if (t1 == TokenTypes.Id)
            {
                SyntaxNode n = parseVar();
                return parseVarTail(n);
            }
            else
            {
                Error("Value expected");
                return null;
            }
        }

        //<idTail>      ::= "[" <exp> "]"
        //                | "." ID
        //                | ""
        private SyntaxNode parseIdTail(SyntaxNode a)
        {
            TokenTypes t1 = TokenAt(0).tokenType;

            if (t1 == TokenTypes.LIndex)
            {
                pos++;
                SyntaxNode n2 = parseExp();
                Expect(TokenTypes.RIndex, "]");

                SyntaxNode table = new SyntaxNode(SyntaxNodeTypes.Table, null);
                table.operands.Add(a);
                table.operands.Add(n2);
                return parseIdTail(table);
            }
            else if (t1 == TokenTypes.Dot)
            {
                pos++;
                Token t = Expect(TokenTypes.Id, "indentifier");

                SyntaxNode table = new SyntaxNode(SyntaxNodeTypes.Table, null);
                table.operands.Add(a);
                table.operands.Add(new SyntaxNode(SyntaxNodeTypes.Constant, "\"" + t.value + "\""));
                return parseIdTail(table);
            }
            else
            {
                return a;
            }
        }

        //<var>         ::= ID <idTail>
        private SyntaxNode parseVar()
        {
            TokenTypes t1 = TokenAt(0).tokenType;
            Token t;

            if (t1 == TokenTypes.Id)
            {
                pos++;
                t = TokenAt(-1);
                return parseIdTail(new SyntaxNode(SyntaxNodeTypes.Variable, "\"" + t.value + "\""));
            }
            else
            {
                Error("variable expected");
                return null;
            }
        }

    }
}
